package org.broadway.monitor;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadway.exceptions.ActionException;
import org.broadway.interfaces.Action;
import org.broadway.interfaces.Context;
import org.broadway.scripting.BsfScriptEngine;
import org.broadway.scripting.BsfScriptRunner;
import org.broadway.scripting.ScriptRunner;
import org.broadway.scripting.ScriptingException;

/**
 * This is an implementation of an Action component.  It's 
 * a specialized action that executes a specified a script file.
 * It uses a ScriptRunner which can be configured to run any
 * number of scripting language supported by the VM.
 * 
 * @author vladimir vivien
 * @see Action
 * @see ScriptRunner
 * 
 */
public class ScriptedAction implements Action<Object> {
	private static Log log = LogFactory.getLog(ScriptedAction.class);
	private BsfScriptRunner runner;
	
	/***
	 * Constructor
	 *
	 */
	public ScriptedAction() {
	}

	/**
	 * Constructor that accepts a default script runner.
	 * @param sr - ScriptRunner.
	 */
	public ScriptedAction(ScriptRunner<Object> sr) {
		runner = (BsfScriptRunner) sr;
	}
	/**
	 * Constructor that accepts a default script file path to be executed.
	 * @param scriptLocation
	 */
	public ScriptedAction(String scriptLocation) {
		this.getScriptRunner().setScriptLocation(scriptLocation);
	}
	
	/**
	 * Setter for the component's script runner.
	 * @param scriptLocation
	 */
	public void setScriptRunner(ScriptRunner<Object> runner) {
		this.runner = (BsfScriptRunner) runner;
	}
	/**
	 * Getter that returns component's ScriptRunner.
	 * @return ScriptRunner
	 */
	public ScriptRunner<Object> getScriptRunner() {
		if(runner == null)
			runner = new BsfScriptRunner(new BsfScriptEngine());
		return this.runner;
	}
	/***
	 * Getter that returns the script file full path.
	 * @return String file location.
	 */
	public String getScriptFileLocation() {
		return ((BsfScriptRunner)getScriptRunner()).getScriptLocation();
	}
	/**
	 * Setter for component's script file path.
	 * @param scriptFileLocation
	 */
	public void setScriptFileLocation(String scriptFileLocation) {
		this.getScriptRunner().setScriptLocation(scriptFileLocation);
		log.debug("Script location set to " + scriptFileLocation);
	}

	/**
	 * This is the implemented Action method.  It runs the script file 
	 * setup in the component to be executed. 
	 * It passes additional parameters to the scripting engine including
	 * <ul>
	 * <li>an instance of the monitor object that orignates the execution call ({@link MonitorConstants.MonitorKeys.MONITOR_OBJECT})</li>
	 * <li>Result of the expression evaluation ({@link context.getValue(MonitorConstants.MonitorKeys.EVALUATOR_RESULT)})</li>
	 * <li>A list of registered resources being monitored ({@link MonitorConstants.MonitorKeys.RESOURCE_COLLECTION})</li>
	 * </ul>
	 * The result of the Action's execution is put back into the context under 
	 * {@link MonitorConstants.MonitorKeys.ACTION_RESULT}.
	 */
	public Object execute(Context context) throws ActionException {
		validate(context);
		this.getScriptRunner().putParameter("context", ((SimpleContext)context).getValues());	
		log.debug("Executing scripted action using file " + this.getScriptFileLocation());
		return this.getScriptRunner().run();

	}

	/***
	 * Private method that determines whether the action should be executed.
	 * @param context
	 */
	private void validate(Context context) {
		if (this.getScriptRunner() == null){
			throw new ScriptingException(
					"Unable to execute Scripted Action - a script runner instance must be provided.");
		}
		
		if(context == null) {
			throw new ScriptingException(
				"Unable to execute Scripted Action - a context instance must be provided and cannot be null.");			
		}
		
		// make sure certain objects are in the context
		Object obj = context.getValue(MonitorConstants.MonitorKeys.MONITOR_OBJECT.toString());
		if(obj == null) {
			throw new ScriptingException(
				"Unable to execute Scripted Action - Unable to find reference for the monitor object in the context.");			
		}
		
		obj = context.getValue(MonitorConstants.MonitorKeys.EVALUATOR_RESULT.toString());
		if(obj == null || !(obj instanceof Boolean)){
			throw new ScriptingException(
				"Unable to execute Scripted Action - the evaluator's result is null or is not a boolean value.");
		}
		
	}

}
